/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

/*! \file Синтаксический анализ
 *
 *  Утилита синтаксического анализа текста на любом языке, заданном грамматикой на языке SIMODO fuze.
 *
 *  Проект SIMODO.
*/

#include "simodo/inout/reporter/ConsoleReporter.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/LibVersion.h"
#include "simodo/inout/token/InputStream.h"
#include "simodo/parser/fuze/fuze_file_extension.h"
#include "simodo/parser/Grammar.h"
#include "simodo/utility/grammatize.h"
#include "simodo/utility/generateDotFile.h"
#include "simodo/parser/automaton/PushdownAutomaton.h"
#include "simodo/variable/json/LexicalParametersLoader.h"
#include "simodo/loom/Loom.h"
#include "simodo/interpret/SemanticOperationsEnumsLoader.h"
#include "simodo/interpret/builtins/AstFormationBuilder.h"
#include "simodo/interpret/builtins/hosts/base/BaseRunning.h"
#include "simodo/interpret/builtins/modules/LexicalParametersModule.h"
#include "simodo/variable/json/Serialization.h"
#include "simodo/variable/convert/Ast.h"

#include <iostream>
#include <fstream>
#include <memory>
#include <algorithm>
#include <chrono>

#if __cplusplus >= __cpp_2017
#include <filesystem>
namespace fs = std::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::filesystem::experimental;
#endif

using namespace std;
using namespace simodo;
 
int main(int argc, char *argv[])
{
    vector<std::string> arguments(argv + 1, argv + argc);

    string  file_name           = "";
    string  json_file_name      = "";
    string  st_dot_file_name    = "";
    string  dot_file_name       = "";
    string  lex_file            = "";
    string  grammar_file        = "";
    string  grammar_path        = "data/grammar";
    string  grammar_builder_method_string = "";
    bool    need_time_intervals = false;
    bool    need_state_transitions_info = false;
    bool    need_rules_info     = false;
    bool    need_st_info        = false;
    bool	error               = false;
    bool	help                = false;
    bool	version             = false;
    bool    need_silence        = true;
    bool    need_build_grammar  = false;
    bool    need_load_grammar   = false;

    for(size_t i=0; i < arguments.size(); ++i)
    {
        const string & arg = arguments[i];

        if (arg[0] == '-')
        {
            if (arg == "--help" || arg == "-h")
                help = true;
            else if (arg == "--version" || arg == "-v")
                version = true;
            else if (arg == "--grammar-builder-method" || arg == "-z")
            {
                if (i == arguments.size()-1 || !grammar_builder_method_string.empty())
                    error = true;
                else
                    grammar_builder_method_string = arguments[++i];
            }
            else if (arg == "--json-semantics" || arg == "-j")
            {
                if (i == arguments.size()-1 || !json_file_name.empty())
                    error = true;
                else
                    json_file_name = arguments[++i];
            }
            else if (arg == "--grammar-file" || arg == "-g")
            {
                if (i == arguments.size()-1 || !grammar_file.empty())
                    error = true;
                else
                    grammar_file = arguments[++i];
            }
            else if (arg == "--lex")
            {
                if (i == arguments.size()-1 || !lex_file.empty())
                    error = true;
                else
                    lex_file = arguments[++i];
            }
            else if (arg == "--path-to-grammar" || arg == "-G")
            {
                if (i == arguments.size()-1)
                    error = true;
                else
                    grammar_path = arguments[++i];
            }
            else if (arg == "--dot-semantics" || arg == "-s")
            {
                if (i == arguments.size()-1 || !st_dot_file_name.empty())
                    error = true;
                else
                    st_dot_file_name = arguments[++i];
            }
            else if (arg == "--dot-grammar" || arg == "-d")
            {
                if (i == arguments.size()-1 || !st_dot_file_name.empty())
                    error = true;
                else
                    dot_file_name = arguments[++i];
            }
            else if (arg == "--state-transitions-info" || arg == "-i")
                need_state_transitions_info = true;
            else if (arg == "--rules-info" || arg == "-r")
                need_rules_info = true;
            else if (arg == "--insertion-info" || arg == "-o")
                need_st_info = true;
            else if (arg == "--time-intervals" || arg == "-t")
                need_time_intervals = true;
            else if (arg == "--silence" || arg == "-S")
                need_silence = true;
            else if (arg == "--dump-grammar" || arg == "-u")
                need_build_grammar = true;
            else if (arg == "--load-grammar" || arg == "-l")
                need_load_grammar = true;
            else
                error = true;
        }
        else if (file_name.empty())
            file_name = arg;
        else
            error = true;
    }

    parser::TableBuildMethod grammar_builder_method = parser::TableBuildMethod::LR1;

    if (grammar_builder_method_string == "lr" || grammar_builder_method_string == "lr1")
        grammar_builder_method = parser::TableBuildMethod::LR1; // -V1048
    else if (grammar_builder_method_string == "slr")
        grammar_builder_method = parser::TableBuildMethod::SLR;
    else if (!grammar_builder_method_string.empty()) {
        cout << "Задан недопустимый метод формирования таблицы грамматики" << endl;
        error = true;
    }

    if (grammar_file.empty() && !file_name.empty()) {
        fs::path file_name_path = file_name;
        if(!file_name_path.extension().empty() && file_name_path.extension().string() != ".") {
            fs::path path_to_grammar = fs::path(grammar_path) / (file_name_path.extension().string().substr(1) + ".fuze");
            grammar_file = path_to_grammar.string();
        }
    }

    if ((grammar_file.empty() || file_name.empty()) && !version && !help)
        error = true;

    if (need_load_grammar && (need_build_grammar || need_time_intervals))
        error = true;

    if (error) {
        cout << "Ошибка в параметрах запуска" << endl;
        help = true;
    }

    const string logo = "Утилита анализа грамматики. Проект SIMODO.";

    if (help)
        cout	<< logo << endl
                << "Формат запуска:" << endl
                << "    simodo-parse [<параметры>] <файл>" << endl
                << "Параметры:" << endl
                << "    -h | --help                   - отображение подсказки по запуску программы" << endl
                << "    -v | --version                - отображение версии программы" << endl
                << "    -z | --grammar-builder-method <метод> - метод построения таблицы грамматики (slr,lr1)" << endl
                << "    -t | --time-intervals         - отображать интервалы времени разбора" << endl
                << "    -j | --json-semantics <путь>  - создать JSON-файл абстрактного дерева операционной семантики" << endl
                << "    -s | --dot-semantics <путь>   - создать DOT-файл абстрактного дерева операционной семантики" << endl
                << "    -d | --dot-grammar <путь>     - создать DOT-файл графа переходов состояний грамматики" << endl
                << "    -r | --rules-info             - вывести перечень правил грамматики" << endl
                << "    -i | --state-transitions-info - вывести информацию о переходах состояний грамматики" << endl
                << "    -o | --insertion-info         - вывести структуры семантических вставок" << endl
                << "    -S | --silence                - не выводить диагностику утилиты" << endl
                << "    -u | --dump-grammar           - построить и сохранить грамматику в дампе" << endl
                << "    -l | --load-grammar           - загрузить грамматику из дампа" << endl
                << "       | --lex <путь>             - путь к файлу описания лексики" << endl
                << "    -g | --grammar-file <путь>    - путь к файлу грамматики" << endl
                << "    -G | --path-to-grammar <путь> - путь к каталогу файлов грамматики" << endl
                << "                                    (конкретная грамматика будет определяться по расширению исходного файла)" << endl
                ;

    if (error)
        return 1;

    if (version)
        cout << logo << endl
             << "Версия: " << lib_version().major() << "." << lib_version().minor() << endl;

    std::ifstream in(grammar_file);

    if (!in) {
        cout << "Ошибка при открытии файла '" << grammar_file << "'" << endl;
        return 1;
    }

    inout::InputStream      in_stream(in);
    inout::ConsoleReporter  m; 
    parser::Grammar         grammar;

    bool ok = utility::grammatize(
                        grammar_file,
                        in_stream,
                        cout,
                        m,
                        "", //json_file_name,
                        "", //st_dot_file_name,
                        grammar_builder_method,
                        dot_file_name,
                        need_state_transitions_info,
                        need_rules_info,
                        need_st_info,
                        need_time_intervals,
                        need_silence,
                        need_build_grammar,
                        need_load_grammar,
                        false, //need_analyze_handles,
                        false, //need_analyze_inserts
                        grammar);
    if (!ok)
        return 1;

    loom::Loom   loom;

    for(const auto & [name, node] : grammar.handlers)
    {
        interpret::Interpret *              inter        = new interpret::Interpret(interpret::InterpretType::Preview, m, loom, {file_name}, node);
        interpret::builtins::BaseRunning *  lexGenerator = new interpret::builtins::BaseRunning(inter);
        std::shared_ptr<interpret::builtins::LexicalParametersModule> 
                                    lex          = std::make_shared<interpret::builtins::LexicalParametersModule>(grammar.lexical);

        /// \todo Пересмотреть странную передачу самого себя в свой же метод.
        /// PVS Studio: warn V678 An object is used as an argument to its own method.
        /// Consider checking the first actual argument of the 'instantiate' function.
        lexGenerator->importNamespace(u"lex", lex->instantiate(lex), inout::null_token_location);
        inter->instantiateSemantics({lexGenerator});
        loom.dock(inter, true);
        loom.stretch(inter);
    }

    loom.finish();

    if (!lex_file.empty() && !variable::loadLexicalParameters(lex_file,grammar.lexical)) {
        cout << "Ошибка при чтении параметров лексики из файла '" << lex_file << "'" << endl;
        return 1;
    }

    variable::VariableSet_t         hosts_and_operations = interpret::loadSemanticOperationsEnums(fs::path(grammar_file).parent_path().string());
    ast::Node                       null_node;
    interpret::Interpret            inter(interpret::InterpretType::Preview, m, loom, {file_name}, null_node);
    interpret::builtins::BaseInterpret_abstract * 
                                    sbl = new interpret::builtins::BaseRunning(&inter);
    interpret::builtins::AstFormationBuilder  
                                    builder(m,*sbl, hosts_and_operations);
    parser::PushdownAutomaton       parser(m, grammar, inter.files(), builder);

    inter.instantiateSemantics({sbl});

    if (!parser.parse())
        return 1;

    if (!st_dot_file_name.empty()) {
        if (!need_silence)
            cout << "Построение DOT-файла..." << std::endl;
        utility::generateDotFile(st_dot_file_name, builder.ast().tree().root(), {});
    }

    if (!json_file_name.empty()) {
        if (!need_silence)
            cout << "Сохранение JSON-файла..." << std::endl;

        ok = variable::saveJson(variable::toValue(builder.ast().tree()), json_file_name, false);

        if (!ok)
            cout << "Ошибка сохранения в '" << json_file_name << "'" << std::endl;
    }

    return 0;
}
